// 
// GradientButton.cs
//  
// Author:
//       bostjan <>
// 
// Copyright (c) 2009 bostjan
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using Cairo;

namespace Kodre.GtkWidgets
{
	
	
	[System.ComponentModel.ToolboxItem(true)]
	public class GradientButton : ButtonBase
	{
		
		#region - Properties -
				
		#region -  Appearance  -
		
		private Cairo.Color startColor = 
			CairoUtils.ColorFromDrawingColor(System.Drawing.Color.AliceBlue);
		public System.Drawing.Color StartColor
			{
			get{return CairoUtils.ColorDrawingFromCairoColor(startColor);}
			set
			{				
				startColor=CairoUtils.ColorFromDrawingColor(value);
				QueueDraw();
			}			
		}
		
		private Cairo.Color endColor = 
			CairoUtils.ColorFromDrawingColor(System.Drawing.Color.LightSteelBlue);
		public System.Drawing.Color EndColor
		{
			get{return CairoUtils.ColorDrawingFromCairoColor(endColor);}
			set
			{
				endColor=CairoUtils.ColorFromDrawingColor(value);
				QueueDraw();
			}			
		}

		private GradientOrientation gradientOrientation= GradientOrientation.Vertical;
		public GradientOrientation GradientOrientation
		{
			get{return gradientOrientation;}
		 	set
			{
				if(gradientOrientation!=value)
				{
					gradientOrientation=value;
					QueueDraw();
				}
			}
		}
		
		private bool gradientMirror=false;
		public bool GradientMirror
		{
			get{return gradientMirror;}
			set
			{
				if(gradientMirror!=value)
				{
					gradientMirror=value;
					QueueDraw();
				}
			}
		}
		
		private int cornerRadius=0;
		public int CornerRadius
		{
			get{return cornerRadius;}
			set
			{
				if(cornerRadius!=value)
				{
					cornerRadius=value;
					if(IsRealized)
						ReshapeWidget();
					QueueDraw();
				}
			}
		}
		
		private Cairo.Color mouseOverColor= 
			CairoUtils.ColorFromDrawingColor(System.Drawing.Color.Beige);
		public System.Drawing.Color MouseOverColor
		{
			get{return CairoUtils.ColorDrawingFromCairoColor(mouseOverColor);}
			set
			{
				mouseOverColor=CairoUtils.ColorFromDrawingColor(value);
			}			
		}
		
		private Cairo.Color mouseDownColor= 
			CairoUtils.ColorFromDrawingColor(System.Drawing.Color.Bisque);	
		public System.Drawing.Color MouseDownColor
		{
			get{return CairoUtils.ColorDrawingFromCairoColor(mouseDownColor);}
			set
			{ 
				mouseDownColor=CairoUtils.ColorFromDrawingColor(value);
			}			
		}
		
		private Cairo.Color borderColor=
			CairoUtils.ColorFromDrawingColor(System.Drawing.Color.Black);
		public System.Drawing.Color BorderColor
		{
			get{return CairoUtils.ColorDrawingFromCairoColor(borderColor);}
			set
			{ 
				borderColor=CairoUtils.ColorFromDrawingColor(value);
				QueueDraw();
			}			
		}
		
		private int borderTickness=1;
		public int BorderTickness
		{
			get {return borderTickness;}
			set 
			{
				borderTickness=value;
				QueueDraw();
			}
		}
		#endregion		
				
		#endregion
				
		public GradientButton ()
		{
			
		}	
		
		protected override void DrawBackground (Cairo.Context cr, Gdk.EventExpose ev)
		{
			Double tick=borderTickness/2.0;
			
			cr.Rectangle(ev.Area.X,ev.Area.Y, ev.Area.Width,ev.Area.Height);
			cr.Clip();
			
			cr.NewPath();
			cr.MoveTo(tick, tick + cornerRadius);
			cr.CurveTo(tick, tick + cornerRadius/2.0,
			           tick + cornerRadius/2.0, tick,
			           tick + cornerRadius,tick);
			cr.LineTo(Allocation.Width - tick - cornerRadius, tick );
			cr.CurveTo(Allocation.Width - tick - cornerRadius/2.0, tick,
			           Allocation.Width - tick, tick + cornerRadius/2.0,
			           Allocation.Width - tick,tick + cornerRadius);
			cr.LineTo(Allocation.Width - tick,Allocation.Height-tick-cornerRadius);
			cr.CurveTo(Allocation.Width - tick, Allocation.Height - tick - cornerRadius/2.0,
			           Allocation.Width - tick - cornerRadius/2.0, Allocation.Height - tick,
			           Allocation.Width - tick - cornerRadius, Allocation.Height - tick );
			cr.LineTo(tick + cornerRadius,Allocation.Height-tick);
			cr.CurveTo(tick  + cornerRadius/2.0, Allocation.Height - tick,
			           tick, Allocation.Height - tick  - cornerRadius/2.0,
			           tick, Allocation.Height - tick  - cornerRadius);
			cr.LineTo(tick,tick + cornerRadius);
			cr.ClosePath ();
			
			if(!mouseDown)
			{
				if(!mouseOver)
				{
					Cairo.LinearGradient linGrad;
					if(gradientOrientation== GradientOrientation.Vertical)
					{
						linGrad= new Cairo.LinearGradient(0,0,0,Allocation.Height);
					}
					else
					{
						linGrad= new Cairo.LinearGradient(0,0,Allocation.Width,0);
					}
					
					if(gradientMirror)
					{
						linGrad.AddColorStop(0,startColor);
						linGrad.AddColorStop(0.5,endColor);
						linGrad.AddColorStop(1,startColor);
					}
					else
					{
						linGrad.AddColorStop(0,startColor);
						linGrad.AddColorStop(1,endColor);
					}
					
					
					cr.Pattern=linGrad;
				}
				else
				{
					cr.Color=mouseOverColor;
				}
			}
			else
			{
				cr.Color=mouseDownColor;
			}
			//cr.Rectangle(ev.Area.X,ev.Area.Y,ev.Area.Width,ev.Area.Height);
			cr.FillPreserve();
		}

		protected override void DrawBorder (Cairo.Context cr, Gdk.EventExpose ev)
		{
			cr.LineWidth=borderTickness;
			cr.Color=borderColor;
			cr.Stroke();
		}
		
		protected override void DrawImage (Cairo.Context cr, Gdk.EventExpose ev)
		{
			if(image!=null)
			{				
				Point pt=GetImagePosition();
				ev.Window.DrawPixbuf(this.Style.ForegroundGC(Gtk.StateType.Normal),image, 0,0,pt.X,pt.Y,image.Width,image.Height, Gdk.RgbDither.Normal, 0,0);
			}
		}

		protected override void DrawText (Cairo.Context cr, Gdk.EventExpose ev)
		{
			Gdk.PangoRenderer renderer = Gdk.PangoRenderer.GetDefault(ev.Window.Screen);
 			renderer.Drawable = ev.Window;
 			renderer.Gc = this.Style.TextGC(Gtk.StateType.Normal);
			renderer.SetOverrideColor(Pango.RenderPart.Foreground, foreColor);
			layout.Width = Pango.Units.FromPixels(this.Allocation.Width);	 	
			Point pt= GetTextPosition();
			renderer.DrawLayout(layout,Pango.Units.FromPixels(pt.X),Pango.Units.FromPixels(pt.Y));
			renderer.SetOverrideColor(Pango.RenderPart.Foreground, Gdk.Color.Zero);
 			renderer.Drawable = null;
 			renderer.Gc = null;
			//ev.Window.DrawLayout(this.Style.TextGC (Gtk.StateType.Normal),pt.X,pt.Y,layout);
		}
		
		/// <summary>
		/// Calculates text position, which depends on TextAllign
		/// </summary>
		/// <returns>
		/// Uper left point of text.
		/// A <see cref="Point"/>
		/// </returns>
		protected override Point GetTextPosition ()
		{
			int h, w;
			layout.GetPixelSize(out w, out h);
			if(textVerticalAlign  == VerticalAllign.Bottom)
				h = this.Allocation.Height - borderTickness - h;
			else if(textVerticalAlign == VerticalAllign.Middle)
				h = this.Allocation.Height/2 - h/2;
			else 
				h = borderTickness;			
			return new Point(0,h);
		}
		
		/// <summary>
		/// Calculates area where text is drawn
		/// </summary>
		/// <returns>
		/// A <see cref="Rectangle"/>
		/// </returns>
		protected override Rectangle GetTextArea ()
		{
			int h, w;
			int y;
			Pango.Rectangle inkRect;
			Pango.Rectangle logRect;
			layout.GetPixelExtents(out inkRect,out logRect);
			layout.GetPixelSize(out w, out h);
			if(textVerticalAlign  == VerticalAllign.Bottom)
				y = this.Allocation.Height - borderTickness - h;
			else if(textVerticalAlign == VerticalAllign.Middle)
				y = this.Allocation.Height/2 - h/2;
			else 
				y = borderTickness;
			return new Rectangle(logRect.X,y,w,h);
		}
		
		protected override Point GetImagePosition ()
		{
			int x=0;
			int y=0;
			int imageOffset=3;
			
			if(image!=null)
			{			
				if((VerticalAllign)((int)imageAllign & (int)VerticalAllign.Bottom) == VerticalAllign.Bottom)
					y = this.Allocation.Height - borderTickness - imageOffset - image.Height;
				else if((VerticalAllign)((int)imageAllign & (int)VerticalAllign.Middle) == VerticalAllign.Middle)
					y = this.Allocation.Height/2 - this.image.Height/2;
				else 
					y = borderTickness + imageOffset;
					
				if((HorizontalAllign)((int)imageAllign & (int)HorizontalAllign.Center) == HorizontalAllign.Center)
					x = this.Allocation.Width / 2 - this.image.Width /2;
				else if((HorizontalAllign)((int)imageAllign & (int)HorizontalAllign.Right) == HorizontalAllign.Right)
				{
					if(cornerRadius<5 || (VerticalAllign)((int)imageAllign & (int)VerticalAllign.Middle) == VerticalAllign.Middle)
						x = this.Allocation.Width - borderTickness - imageOffset - this.image.Width;
					else
						x = this.Allocation.Width - borderTickness - imageOffset - this.image.Width -(cornerRadius/3)*2;
				}
				else 
				{
					if(cornerRadius<5 || (VerticalAllign)((int)imageAllign & (int)VerticalAllign.Middle) == VerticalAllign.Middle)
						x = borderTickness + imageOffset;
					else
						x=(cornerRadius/3)*2;
				}
			}
			
			return new Point(x,y);
		}
				
		protected override void OnSizeAllocated (Gdk.Rectangle allocation)
		{			
			base.OnSizeAllocated (allocation);
			//on size allocation changed, new widget shape must be applied 
			if(IsRealized){
				ReshapeWidget();			
			}
		}		
		
		protected override void OnRealized ()
		{
			base.OnRealized ();
			//on this event gdk.window is associated with a widget, so our shape must be applied to it
			ReshapeWidget();
		}
		
		/// <summary>
		/// Method that apply new shape to widget
		/// </summary>
		private void ReshapeWidget()
		{
			Gdk.Pixmap pix= new Gdk.Pixmap( this.GdkWindow ,this.Allocation.Width,this.Allocation.Height,1);
			
			
			Cairo.Context crPix =  Gdk.CairoHelper.Create(pix);			
			crPix.Operator = Operator.Source;
			crPix.Source = new SolidPattern (new Cairo.Color (0,0,0,0));
			crPix.Rectangle (0, 0, this.Allocation.Width, this.Allocation.Height);
			crPix.Paint ();
			
			crPix.SetSourceRGB(1.0,1.0,1.0);
			crPix.Operator= Cairo.Operator.Over;
			crPix.NewPath();
			crPix.MoveTo(0, cornerRadius);
			crPix.CurveTo(0, cornerRadius/2.0,
			           cornerRadius/2.0, 0,
			           cornerRadius,0);
			crPix.LineTo(Allocation.Width - cornerRadius, 0 );
			crPix.CurveTo(Allocation.Width - cornerRadius/2.0, 0,
			           Allocation.Width, cornerRadius/2.0,
			           Allocation.Width , cornerRadius);
			crPix.LineTo(Allocation.Width ,Allocation.Height-cornerRadius);
			crPix.CurveTo(Allocation.Width , Allocation.Height - cornerRadius/2.0,
			           Allocation.Width - cornerRadius/2.0, Allocation.Height ,
			           Allocation.Width -  cornerRadius, Allocation.Height );
			crPix.LineTo(cornerRadius,Allocation.Height);
			crPix.CurveTo(cornerRadius/2.0, Allocation.Height,
			           0, Allocation.Height - cornerRadius/2.0,
			           0, Allocation.Height - cornerRadius);
			crPix.LineTo(0,cornerRadius);
			crPix.ClosePath ();
			crPix.FillPreserve();
			crPix.StrokePreserve();
			crPix.Save();
			
			GdkWindow.ShapeCombineMask(pix,0,0);
			
			((IDisposable) crPix.Target).Dispose ();
        	((IDisposable) crPix).Dispose ();
		}

		protected override void OnSizeRequested (ref Gtk.Requisition requisition)
		{
			// Calculate desired size here.
			requisition.Height = 50;
			requisition.Width = 50;
		}
	}
}
